﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AlgorithmResult;
using System.Diagnostics;
using SimilarityToAncientCodon;

namespace SegGen
{
    public class SegGenAlgorithm
    {
        

        Population current_Population_Pt = null;
        Population external_Archive_P = null;
        Population temporary_Population = null;

        public enum NUCLEOTID { A = 0, T = 1, C = 2, G = 3 };

        public event ProgressEventHandler ProgressHandler = null;
        
        
        private bool isRunSegGen = true;
         
        private int m_numNuclInCodon;

        public int NumNuclInCodon
        {
            get { return m_numNuclInCodon; }
            set { m_numNuclInCodon = value; }
        }

        private String m_gen;

        public String Gen
        {
            get { return m_gen; }
            set { m_gen = value; }
        }

        private int m_minSegmentSize;

        public int MinSegmentSize
        {
            get { return m_minSegmentSize; }
            set { m_minSegmentSize = value; }
        }
        

        public Population Temporary_Population
        {
            get { return temporary_Population; }
            set { temporary_Population = value; }
        }

    

       

        public static Random rand = new Random((int)DateTime.Now.Ticks);

        public SegGenAlgorithm(double _crossoverFraction, double _mutationFraction, double _reproductionFraction,
            int _numTopIndividuals, int numNucleotidesInCodon)
        {
            m_numNuclInCodon = numNucleotidesInCodon;
            //double _crossoverFraction = 0.8; 
            //double _mutationFraction = 0.05; 
            //double _reproductionFraction = 0.7;
            //int    _numTopIndividuals = 2;
            current_Population_Pt = new Current_Population_Pt(numNucleotidesInCodon);
            external_Archive_P = new External_Archive_P(numNucleotidesInCodon);

            current_Population_Pt.NeighbourPopulation = external_Archive_P;
            external_Archive_P.NeighbourPopulation = current_Population_Pt;

            temporary_Population = new Temporary_Population(_crossoverFraction, _mutationFraction, _reproductionFraction, _numTopIndividuals, numNucleotidesInCodon);

            temporary_Population.NeighbourPopulation = external_Archive_P;
            temporary_Population.NeighbourPopulation2 = current_Population_Pt;
        }

        public Result startSegGen(String gen, int numGenerations, int numIndividuals, int minSegmentSize,double alpha)
        {

            m_gen = gen;
            m_minSegmentSize = minSegmentSize;

            int maxNumBoundariesInIndividual = ((m_gen.Length / m_numNuclInCodon) / minSegmentSize);

          List<Individual> extArchList = new List<Individual>();
          List<Individual> curPopList = new List<Individual>();
          List<Individual> tempPopList = new List<Individual>();

          Stopwatch sw = new Stopwatch();
          sw.Start();

                // to initialize the seg gen
          initSegGen(gen, numIndividuals, maxNumBoundariesInIndividual, minSegmentSize,alpha);

          Console.WriteLine("After initSegGen Time: {0} ms", sw.ElapsedMilliseconds);
          sw.Stop();

          Stopwatch sw2 = new Stopwatch();
          sw2.Start();

          external_Archive_P.calculateFitness(current_Population_Pt);

          //Console.WriteLine("After calculateFitness Time: {0} ms", sw2.ElapsedMilliseconds);
          sw2.Stop();

          Stopwatch sw3 = new Stopwatch();
        
                 // TODO:  if there is no improvement then stop generations
                for (int i = 0; i < numGenerations && isRunSegGen == true; i++)
                {
                    sw3.Start();
                    // make selection from current population and from external archive
                    temporary_Population.Selection(current_Population_Pt.Individuals_List,
                    external_Archive_P.Individuals_List);

                 
                    // make crossover
                    temporary_Population.Crossover();

                    // make mutate  
                    temporary_Population.Mutate();

                    current_Population_Pt.Update(temporary_Population);

                    // after crossover, mutation calculate fitness values
                    // for external archive and current population
                    external_Archive_P.calculateFitness(current_Population_Pt);

                    //Console.WriteLine("One Generation Time: {0} ms", sw3.ElapsedMilliseconds);
                    sw3.Stop();

                    // progress bar - TODO: change progress
                    if (ProgressHandler != null)
                    {
                        ProgressHandler(this, new progressEvent(i, null));
                    }

                    Console.WriteLine("Generation:" + i );
                    Console.WriteLine("Current pop size:" + current_Population_Pt.Individuals_List.Count);
                    Console.WriteLine("Ext pop size:" + external_Archive_P.Individuals_List.Count);

                    //Individual bestIndividualTemp = current_Population_Pt.ExtractionOfSolution();

                    //if (bestIndividualTemp != null)
                    //{
                    //    System.Console.WriteLine("Generation : " + i + "   Individual: " + bestIndividualTemp.IndividualID);
                    //    bestIndividualTemp.Print();
                    //}
                }
                // after number of generations, 
                // extract best solution from external archive 

       //         current_Population_Pt.printAll();
       //         external_Archive_P.printAll();
                Individual bestIndividual = null;
                Individual cur_bestIndividual = current_Population_Pt.ExtractionOfSolution();
                Individual arc_bestIndividual = external_Archive_P.ExtractionOfSolution();
            
                if (cur_bestIndividual != null && arc_bestIndividual != null) {
                    if (cur_bestIndividual.Agg > arc_bestIndividual.Agg)
                    {
                        bestIndividual = cur_bestIndividual;
                    }
                    else {
                        bestIndividual = arc_bestIndividual;
                    }
                }
                else if (cur_bestIndividual == null && arc_bestIndividual != null)
                {
                    bestIndividual = arc_bestIndividual;
                }
                else
                {
                    bestIndividual = cur_bestIndividual;
                }
           System.Console.WriteLine("Before Optimize The best Individual id is: " + bestIndividual.IndividualID);
           bestIndividual.Print();
           bestIndividual.Optimize();
           System.Console.WriteLine("After OptimizeThe best Individual id is: " + bestIndividual.IndividualID);
           bestIndividual.Print();
         return  convertToAlgorithmResult(bestIndividual);
           
       
        //    convertToAlgorithmResult(bestIndividual);
        }

        public Result stopSegGen()
        {
            isRunSegGen = false;
            Individual bestIndividual = external_Archive_P.ExtractionOfSolution();
           
            return convertToAlgorithmResult(bestIndividual);
        }



        private Result convertToAlgorithmResult(Individual bestIndividual)
        {
            try
            {
                long[] boundariesSet = null;
                String[] archreTriplets = null;
                int numOfBoundaries = 0;
                // this array will display the similarity to the ancient codon
                double[] similarityToAncientCodon = null; 

                if (bestIndividual == null) return null;

                // update number of boundaries
                if (bestIndividual.IndividualSegmentList.Count > 0)
                {
                    numOfBoundaries = bestIndividual.IndividualSegmentList.Count;
                    boundariesSet = new long[numOfBoundaries];
                    similarityToAncientCodon = new double[numOfBoundaries];
                }

                if (boundariesSet == null)
                    return null;

                // sort according to start index
                bestIndividual.IndividualSegmentList.Sort();
                // convert to long []boundariesSet
                for (int i = 0; i < numOfBoundaries; i++)
                {
                    boundariesSet[i] = bestIndividual.IndividualSegmentList.ElementAt(i).SegmentEndIndex * m_numNuclInCodon;
                }

                // 
                archreTriplets = new String[bestIndividual.IndividualSegmentList.Count];
                int[][] segmentDest = AllocDimensionArray(4, m_numNuclInCodon);

                // calculate array of codons in segment
                for (int i = 0; i < bestIndividual.IndividualSegmentList.Count; i++)
                {
                    Segment curSegment = bestIndividual.IndividualSegmentList.ElementAt(i);

                    for (int numCodonInSegmnt = 0; numCodonInSegmnt < curSegment.SegmentSize; numCodonInSegmnt++)
                    {
                        String triplet = m_gen.Substring((curSegment.SegmentStartIndex + numCodonInSegmnt) * m_numNuclInCodon, m_numNuclInCodon);
                        AddTripletToSegment(segmentDest, triplet);
                    }

                    EvaluateCostFunction(segmentDest, curSegment.SegmentSize, ref archreTriplets[i]);
                //    calculateSimilarityToAncientCodon(curSegment, archreTriplets[i], similarityToAncientCodon[i]);

                    ZeroSegNuclDistribution(segmentDest);
                }// end for


                Result bestResultOfSegGen = new Result(boundariesSet, archreTriplets, numOfBoundaries);
                FragmentSimToAncientCodon simToAncientCodon = new FragmentSimToAncientCodon(bestResultOfSegGen, m_gen,m_numNuclInCodon);
                return bestResultOfSegGen;
            }
            catch (Exception ex)
            {

                System.Console.WriteLine("Result error: " + ex.Message + "  " + ex.StackTrace);
            }

            return null;
        }
/******************************************************************************************/
        private void ZeroSegNuclDistribution(int[][] SegLeftNuclDistr)
        {
            for (int i = 0; i < SegLeftNuclDistr.Length; ++i)
                for (int j = 0; j < SegLeftNuclDistr[i].Length; j++)
                    SegLeftNuclDistr[i][j] = 0;
        }

        private void AddTripletToSegment(int[][] segmentDest, String triplet)
        {

            for (int index = 0; index < m_numNuclInCodon; index++)
            {
                segmentDest[CnvtNucliotideToIndx(triplet[index])][index]++;
            }
        }
        private int CnvtNucliotideToIndx(char nucliotide)
        {
            int index = 0;
            if (Enum.IsDefined(typeof(NUCLEOTID), Convert.ToString(nucliotide).ToUpper()))
                index = (int)Enum.Parse(typeof(NUCLEOTID), Convert.ToString(nucliotide), true);

            return index;
        }

        private double EvaluateCostFunction(int[][] SegmentOfTriplets, int segmentSize, ref String archeTriplet)
        {
            int cost = 0;
            int[] maxNuclDist = new int[m_numNuclInCodon];
            int i = 0, j = 0;
            archeTriplet = String.Empty;
            String nucl = String.Empty;
            //triplet size is 3
            while (i < m_numNuclInCodon)
            {
                j = 0;
                while (j < 4)
                {//4-number of nucleotides
                    if (maxNuclDist[i] < SegmentOfTriplets[j][i])
                    {
                        maxNuclDist[i] = SegmentOfTriplets[j][i];
                        nucl = ((NUCLEOTID)j).ToString();
                    }
                    j++;
                }
                cost += maxNuclDist[i];
                archeTriplet += nucl;
                i++;
            }
            return (double)cost / segmentSize;
        }

        private int[][] AllocDimensionArray(int rows, int columns)
        {
            int[][] dimensionArray = new int[rows][];
            for (int i = 0; i < rows; i++)
            {
                dimensionArray[i] = new int[columns];
            }
            return dimensionArray;
        }

/*****************************************************************************************/


      private void initSegGen(String gen,int numIndividuals,int numBoundariesInIndividual, 
          int minSegmentSize,double alpha)
      {
          //String gen;
          //gen = //"GAAGCTGCAGTAGAAAAATTCGGTGTAGCTACTGCTGCTGTTGCTGCCGCTGCT";
          //    //"GAAGCTGCAGTAGAAAAATTCGGTGTAGCT";
          //    "AAAAAAAAAAAAAAAGGGGGGGGGGGGTTTTTTTTTTTTTTT";
          //int numIndividuals = 10;
          //int numBoundariesInIndividual = 3;
          //int minSegmentSize = 4;
          //int numNucleotidesInCodon = 3;
         
          

          try
          {
              createRandomIndividuals(ref gen, numIndividuals, numBoundariesInIndividual, minSegmentSize,alpha);
              
          }
          catch (NumBoundariesException ex)
          {
              System.Console.WriteLine("initSegGen - NumBoundaries Exception");
          }
      }

      private void createRandomIndividuals(ref String gene, int numIndividuals, int numBoundariesInIndividual, int minSegmentSize, double alpha)
      {


          external_Archive_P.CreateRandomIndividuals(ref gene, numIndividuals, numBoundariesInIndividual, minSegmentSize,alpha);
           
          
      }


        // print population
      public void printPopulation()
      {
          external_Archive_P.printPopulation();
        
      }

    }
}
